﻿using System.CodeDom;
using System.Linq;
using AopCore;
using Roslyn.Compilers.CSharp;

namespace WeaverCustomTool
{
    public struct MappingPropertyDeclaration
    {
        private readonly CodeMemberProperty property;
        private readonly CodeMemberField field;

        public MappingPropertyDeclaration(CodeMemberProperty property, CodeMemberField field)
        {
            this.property = property;
            this.field = field;
        }

        /// <summary>
        /// Implements the INotify property changed pattern in setter.
        /// </summary>
        /// <param name="mapAttribute">The map attribute.</param>
        /// <param name="type">The type.</param>
        public void ImplementCommandMappingOntype(AttributeSyntax mapAttribute, CodeTypeDeclaration type)
        {
            if (mapAttribute != null)
            {
                var arguments = from arg in mapAttribute.ArgumentList.Arguments
                                select new
                                {
                                    name = arg.NameEquals == null ? null : arg.NameEquals.Name.Identifier.ValueText,
                                    expression = arg.Expression
                                };
                var typeArgument = arguments.FirstOrDefault(arg => arg.name == null || arg.name == "Type");
                if (typeArgument != null && typeArgument.expression is TypeOfExpressionSyntax)
                {
                    // InitializeComponent method recovering or creation
                    var initializeComponentMethod = new MethodDeclaration(type).GetMethodOrDefault("InitializeComponent");
                    if (initializeComponentMethod == null)
                    {
                        initializeComponentMethod = new CodeMemberMethod { Name = "InitializeComponent" };
                        type.Members.Add(initializeComponentMethod);
                        var constructor = type.Members.OfType<CodeConstructor>().FirstOrDefault();
                        if (constructor == null)
                        {
                            constructor = new CodeConstructor();
                            type.Members.Add(constructor);
                        }

                        constructor.Statements.Add(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), initializeComponentMethod.Name));
                    }

                    // Targetted type instance reference creation
                    var typeSyntax = ((TypeOfExpressionSyntax)typeArgument.expression).Type;
                    var typeValueText = typeSyntax.ToFullString().Trim();
                    var fieldType = new CodeTypeReference(typeValueText);
                    var fieldInstanceName = typeSyntax.CreateFieldName();
                    if (type.Members.OfType<CodeMemberField>().All(f => f.Name != fieldInstanceName))
                    {
                        var fieldTypeInstance = new CodeMemberField(fieldType, fieldInstanceName);
                        initializeComponentMethod.Statements.Add(
                            new CodeAssignStatement(
                                new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldTypeInstance.Name), 
                                new CodeObjectCreateExpression(fieldType, new CodeThisReferenceExpression())));
                        type.Members.Add(fieldTypeInstance);
                    }

                    // Command mapping assignement
                    var methodName = property.Name.EndsWith("Command") ? property.Name.Remove(property.Name.Length - "Command".Length) : property.Name;
                    var mappedTargetMemberReference = new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldInstanceName), methodName);
                    var mapperCreation = new CodeObjectCreateExpression(typeof(CommandMapper), mappedTargetMemberReference);
                    var mapperassignation = new CodeAssignStatement(
                            new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), this.field.Name),
                            mapperCreation);

                    // Add the mapping assignation to the constructor
                    initializeComponentMethod.Statements.Add(mapperassignation);
                }
            }
        }
    }
}
